# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

from coin.base.book import BookBuilderBase
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct


class BookEntry(object):
  def __init__(self, price, qty, count):
    self.price = float(price)
    self.qty = float(qty)
    self.count = count


class BookBuilder(BookBuilderBase):
  ProductType = BitfinexProduct

  def __init__(self, native_symbol, book_builder_observer=None):
    super().__init__("Bitfinex", native_symbol, book_builder_observer=book_builder_observer)
    self.ready = False

  def snapshot(self, message: list):
    self.ready = True
    self._ask_price_map.clear()
    self._bid_price_map.clear()
    for data in message:
      price, count, qty = data
      self._insert(price, count, qty, True)

  def update(self, message: list):
    if not self.ready or not message:
      return False
    price, count, qty = message
    if count == 0:
      # delete
      self._delete(price, count, qty)
    elif count > 0:
      self._insert(price, count, qty)
    else:
      raise ValueError('Negative count')
    return True

  def _insert(self, price, count, qty, insert_only=False):
    assert count > 0
    entry = BookEntry(price, abs(qty), count)
    if qty > 0:  # bid
      if insert_only:
        assert price not in self._bid_price_map
      self._bid_price_map[price] = entry
    elif qty < 0:  # ask
      if insert_only:
        assert price not in self._ask_price_map
      self._ask_price_map[price] = entry
    self.remove_cross_book_level(price, qty)

  def _delete(self, price, count, qty):
    # TODO(inkyu): Live or die?
    if qty == 1:  # bid
      if price in self._bid_price_map:
        del self._bid_price_map[price]
    elif qty == -1:  # ask
      if price in self._ask_price_map:
        del self._ask_price_map[price]
    else:
      raise ValueError('qty must be 1 or -1')

  def remove_cross_book_level(self, price, qty):
    if qty < 0:  # ask
      if self.bid0() is not None and price <= self.bid0().price:
        self._delete_bid_up_to_and_including(price)
    elif qty > 0:  # bid
      if self.ask0() is not None and price >= self.ask0().price:
        self._delete_ask_up_to_and_including(price)
    else:
      raise ValueError('Invalid case, qty=0.')

  def _delete_bid_up_to_and_including(self, price):
    elist = self._bid_price_map.get_entries(price, float('Inf'), exclude_max=False)
    for entry in elist:
      del self._bid_price_map[entry.price]

  def _delete_ask_up_to_and_including(self, price):
    elist = self._ask_price_map.get_entries(-float('Inf'), price, exclude_max=False)
    for entry in elist:
      del self._ask_price_map[entry.price]
